package se.webbzon.boltzmann.game.object.portal;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;

import javax.media.j3d.Transform3D;
import javax.vecmath.AxisAngle4d;
import javax.vecmath.Vector3d;

import se.webbzon.boltzmann.effect.ScreenTransitionEffect;
import se.webbzon.boltzmann.environment.AbstractEnvironment;
import se.webbzon.boltzmann.game.object.action.AbstractAction;
import se.webbzon.boltzmann.game.object.player.AbstractPlayerCharacter;
import se.webbzon.boltzmann.game.object.waypoint.AbstractDirection;
import se.webbzon.boltzmann.game.object.waypoint.DirectionListener;
import se.webbzon.boltzmann.game.object.waypoint.PlayerDirection;
import se.webbzon.boltzmann.game.object.waypoint.Waypoint;
import se.webbzon.boltzmann.game.switchable.Switchable;
import se.webbzon.boltzmann.resource.Resources;
import se.webbzon.oschi01.worldarea.WorldArea;
import se.webbzon.oschi01.worldeditor.DeflatableWorldObject;
import se.webbzon.oschi01.worldeditor.WorldObjectDeflation;
import se.webbzon.oschi01.worldobject.WorldObject;
import se.webbzon.oschi01.worldobject.appearance.Appearance3D;
import se.webbzon.oschi01.worldobject.appearance.SimpleAppearance3D;
import se.webbzon.oschi01.worldobject.appearance.SimpleGroupAppearance3D;
import se.webbzon.oschi01.worldobject.appearance.XZPlaneAppearance3D;
import se.webbzon.oschi01.worldobject.appearance.YZPlaneAppearance3D;

public class DoorPortal extends AbstractPortal implements DeflatableWorldObject, Switchable {
	
	// The name of the deflatable world object
	public static final String worldObjectName = "doorPortal";
	
	// The state of the portal
	private static enum State {IDLE, LEAVING, ENTERING};
	
	// The door associated with this abstract door portal
	private final AbstractDoor door;
	
	// The abstract action used to open the door
	private final AbstractAction<DoorPortal> action;
	
	// The door type
	private DoorEnum doorType;
	
	// The name of the texture set used for the door
	private String textureSetName;
	
	// The blending of the texture used for the door
	private Color textureBlending;
	
	// The state of the portal
	private State state;
		
	// The transition effect for this road portal
	private ScreenTransitionEffect transition;
	
	// The player associated to to transition
	private AbstractPlayerCharacter player;

	// The player blocked
	private Object blocker;
	
	/*============================================================
	Constructors
	============================================================*/

	/** Creates a new door portal. **/
	public DoorPortal(	AbstractEnvironment env, 
						String worldAreaName, byte portalId,
						DoorEnum doorType, 
						String textureSetName, Color textureBlending) {
		super(	WorldObject.onCreate + WorldObject.onStep + 
				WorldObject.onDestroy, createAppearance(),
				env, worldAreaName, portalId);
		this.doorType = doorType;
		this.textureSetName = textureSetName;
		this.textureBlending = textureBlending;
		updateAppearance();
		if (env.isDebugMode()) {
			door = null;
			setVisible(true);
		} else {
			door = createDoor(doorType,textureSetName,textureBlending);
			setVisible(false);
		}
		action = new AbstractActionImpl(env);
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Returns the type of the door used for this door portal. **/
	public DoorEnum getDoorType() {
		return doorType;
	}

	/** Sets the type of the door used for this door portal. **/
	public void setDoorType(DoorEnum doorType) {
		this.doorType = doorType;
	}

	/** Returns the name of the texture set used for this door portal. **/
	public String getTextureSetName() {
		return textureSetName;
	}

	/** Sets the name of the texture set used for this door portal. **/
	public void setTextureSetName(String textureSetName) {
		this.textureSetName = textureSetName;
		updateAppearance();
	}
	
	/** Returns the blending of the texture used for this door portal. **/
	public Color getTextureBlending() {
		return textureBlending;
	}

	/** Sets the blending of the texture used for this door portal. **/
	public void setTextureBlending(Color textureBlending) {
		this.textureBlending = textureBlending;
		updateAppearance();
	}

	@Override public void setPosition(double x, double y, double z) {
		super.setPosition(x, y, z);
		if (door != null)
			door.setPosition(x, y, z);
	}

	@Override public void setRotation(double x, double y, double z, double angle) {
		super.setRotation(x, y, z, angle);
		if (door != null)
			door.setRotation(x, y, z, angle);
	}

	@Override public void setSize(double width, double height, double depth) {
		super.setSize(width, height, depth);
		if (door != null)
			door.setSize(width, height, depth);
	}

	@Override public void onCreate(WorldArea world) {
		super.onCreate(world);
		state = State.IDLE;
		if (!getEnvironment().isDebugMode()) {
			world.createInstance(door);
			if (!isOneWayPortal())
				world.createInstance(action);
		}
	}

	@Override public void onStep(WorldArea world) {
		if (state == State.ENTERING) {
			// Enter the world area via the portal
			if (transition.getDuration() <= 0) {
				player.releaseBlocker(blocker);
				player.setMotion(0, 0);
				state = State.IDLE;
				door.setEnable(false, false);
			}
			
		} else if (state == State.LEAVING) {
			// Leave the world area via the portal
			if (transition.getDuration() <= 0) {
				teleport(player.getPlayer());
				state = State.IDLE;
			}
			
		} 
	}
	
	@Override public void onDestroy(WorldArea world) {
		super.onDestroy(world);
		if (!getEnvironment().isDebugMode()) {
			world.destroyInstance(door);
			if (!isOneWayPortal())
				world.destroyInstance(action);
		}
	}

	@Override public void spawn(AbstractPlayerCharacter player) {
		door.setEnable(true, true);
		this.player = player;
		player.setPosition(getX(),getY() - getHeight()/2 + player.getHeight()/2, getZ());
		player.setFacing(getAngle() + Math.PI);
		blocker = player.containBlocker();
		player.setMotion(getAngle() + Math.PI, 1.5);
		state = State.ENTERING;
		transition = new ScreenTransitionEffect(ScreenTransitionEffect.FADE + 
												ScreenTransitionEffect.REVERSED,
												"transition_square",20);
		transition.launch(getWorld());
	}
	
	@Override public void setEnable(boolean enable, boolean instant) {
		door.setEnable(enable, instant);
	}

	@Override public boolean isEnabled() {
		return door.isEnabled();
	}
	
	@Override public String getName() {
		return worldObjectName;
	}

	@Override public WorldObjectDeflation getDeflation() {
		return new DoorPortalDeflation(0,this);
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Creates a new door with a given set of properties. **/
	private static final AbstractDoor createDoor(DoorEnum door, String textureSetName, Color textureBlending) {
		switch (door) {
		case LEFT: return new SingleDoor(true,false,textureSetName,textureBlending);
		case RIGHT: return new SingleDoor(false,false,textureSetName,textureBlending);
		case DOUBLE: return new DoubleDoor(false,textureSetName,textureBlending);
		case LEFT_INDOOR: return new SingleDoor(true,true,textureSetName,textureBlending);
		case RIGHT_INDOOR: return new SingleDoor(false,true,textureSetName,textureBlending);
		case DOUBLE_INDOOR: return new DoubleDoor(true,textureSetName,textureBlending);
		default: return null;
		}
	}
	
	/** Creates the appearance of the door portal. (Only used in
	 * debugging mode). **/
	private static final Appearance3D createAppearance() {
		List<Appearance3D> appearances = new ArrayList<Appearance3D>();
		
		final Transform3D transform = new Transform3D();
		final Vector3d vector = new Vector3d(0,-0.5,0);
		final AxisAngle4d rotation = new AxisAngle4d(0,1,0,Math.PI);
		
		// Create portal base
		SimpleAppearance3D base = 
				new XZPlaneAppearance3D(0,SimpleAppearance3D.LAYERS,false);
		base.setTextureSet(Resources.getTextureSet("misc_arrow"));
		base.setLayer(0, 1, 0);
		transform.setTranslation(vector);
		base.setTransform(transform);
		appearances.add(base);
		
		// Create portal doors (front and back)
		for (int i = 0; i < 2; i++){
			SimpleAppearance3D doorFront = 
					new YZPlaneAppearance3D(0,	SimpleAppearance3D.LAYERS + 
												SimpleAppearance3D.TEXTURE_REPEATING,i == 1);
			doorFront.setLayer(-1, 0, 0);
			vector.set(0.5, 0, 0);
			transform.setTranslation(vector);
			transform.setRotation(rotation);
			doorFront.setTransform(transform);
			appearances.add(doorFront);
		}
		
		return new SimpleGroupAppearance3D(0,appearances);
	}
	
	/** Updates the appearance by using the texture set name and
	 * the blending of this door portal. **/
	private final void updateAppearance() {
		SimpleGroupAppearance3D appearance = (SimpleGroupAppearance3D) getAppearance();
		for (int i = 1; i < 3; i++) {
			SimpleAppearance3D door = (SimpleAppearance3D) appearance.getAppearance(i);
			door.setTextureSet(Resources.getTextureSet(textureSetName));
			door.setColor(textureBlending);
			if (doorType == DoorEnum.DOUBLE || doorType == DoorEnum.DOUBLE_INDOOR)
				door.setHRep(-2);
			else
				door.setHRep(-1);
		}
	}
	
	/*============================================================
	Private Classes
	============================================================*/
	
	/** An implementation of an abstract action used to open the door. **/
	private final class AbstractActionImpl extends AbstractAction<DoorPortal> {

		/*============================================================
		Constructors
		============================================================*/
		
		public AbstractActionImpl(AbstractEnvironment env) {
			super(0, env, DoorPortal.this, Color.green, true, 32);
		}
		
		/*============================================================
		Public Methods
		============================================================*/

		@Override public void onActionBegin(DoorPortal parent, AbstractPlayerCharacter player) {
			Waypoint wp = new Waypoint(getEnvironment());
			final double angle = parent.getAngle() - Math.PI;
			wp.setPosition(	parent.getX() + 32*Math.cos(angle), 
							parent.getY(), 
							parent.getZ() - 32*Math.sin(angle));
			PlayerDirection direction = new PlayerDirection(player,wp);
			direction.addListener(new DirectionListenerImpl());
			direction.begin(getWorld());
			DoorPortal.this.player = player;
		}
		
	}
	
	/** An implementation of a direction listener **/
	private final class DirectionListenerImpl implements DirectionListener {
		
		/*============================================================
		Public Methods
		============================================================*/

		@Override public void targetReached(AbstractDirection direction) {
			door.setEnable(true, false);
			blocker = player.containBlocker();
			final double dir = Math.atan2(	getZ() - player.getZ(),
											getX() - player.getX());
			player.setMotion(-dir, player.getGroundSpeed());
			transition = 
					new ScreenTransitionEffect(	
							ScreenTransitionEffect.FADE,
							"transition_square",15);
			transition.launch(getWorld());
			state = State.LEAVING;
			
		}
		
	}

}
